home *** CD-ROM | disk | FTP | other *** search
/ Chip 2006 June / CHIP 2006-06.2.iso / program / freeware / Democracy-0.8.2.exe / xulrunner / python / BitTorrent / Rerequester.py < prev    next >
Encoding:
Python Source  |  2006-04-10  |  9.0 KB  |  242 lines

  1. # The contents of this file are subject to the BitTorrent Open Source License
  2. # Version 1.0 (the License).  You may not copy or use this file, in either
  3. # source code or executable form, except in compliance with the License.  You
  4. # may obtain a copy of the License at http://www.bittorrent.com/license/.
  5. #
  6. # Software distributed under the License is distributed on an AS IS basis,
  7. # WITHOUT WARRANTY OF ANY KIND, either express or implied.  See the License
  8. # for the specific language governing rights and limitations under the
  9. # License.
  10.  
  11. # Written by Bram Cohen
  12.  
  13. from threading import Thread
  14. from socket import error, gethostbyname
  15. from time import time
  16. from random import random, randrange
  17. from binascii import b2a_hex
  18.  
  19. from BitTorrent.zurllib import urlopen, quote, Request
  20. from BitTorrent.btformats import check_peers
  21. from BitTorrent.bencode import bdecode
  22. from BitTorrent import BTFailure, INFO, WARNING, ERROR, CRITICAL
  23.  
  24.  
  25. class Rerequester(object):
  26.  
  27.     def __init__(self, url, config, sched, howmany, connect, externalsched,
  28.             amount_left, up, down, port, myid, infohash, errorfunc, doneflag,
  29.             upratefunc, downratefunc, ever_got_incoming, diefunc, sfunc):
  30.         self.baseurl = url
  31.         self.infohash = infohash
  32.         self.peerid = None
  33.         self.wanted_peerid = myid
  34.         self.port = port
  35.         self.url = None
  36.         self.config = config
  37.         self.last = None
  38.         self.trackerid = None
  39.         self.announce_interval = 30 * 60
  40.         self.sched = sched
  41.         self.howmany = howmany
  42.         self.connect = connect
  43.         self.externalsched = externalsched
  44.         self.amount_left = amount_left
  45.         self.up = up
  46.         self.down = down
  47.         self.errorfunc = errorfunc
  48.         self.doneflag = doneflag
  49.         self.upratefunc = upratefunc
  50.         self.downratefunc = downratefunc
  51.         self.ever_got_incoming = ever_got_incoming
  52.         self.diefunc = diefunc
  53.         self.successfunc = sfunc
  54.         self.finish = False
  55.         self.current_started = None
  56.         self.fail_wait = None
  57.         self.last_time = None
  58.         self.previous_down = 0
  59.         self.previous_up = 0
  60.  
  61.     def _makeurl(self, peerid, port):
  62.         return ('%s?info_hash=%s&peer_id=%s&port=%s&key=%s' %
  63.                 (self.baseurl, quote(self.infohash), quote(peerid), str(port),
  64.                  b2a_hex(''.join([chr(randrange(256)) for i in xrange(4)]))))
  65.  
  66.     def change_port(self, peerid, port):
  67.         self.wanted_peerid = peerid
  68.         self.port = port
  69.         self.last = None
  70.         self.trackerid = None
  71.         self._check()
  72.  
  73.     def begin(self):
  74.         self.sched(self.begin, 60)
  75.         self._check()
  76.  
  77.     def announce_finish(self):
  78.         self.finish = True
  79.         self._check()
  80.  
  81.     def announce_stop(self):
  82.         self._announce(2)
  83.  
  84.     def _check(self):
  85.         if self.current_started is not None:
  86.             if self.current_started <= time() - 58:
  87.                 self.errorfunc(WARNING, "Tracker announce still not complete "
  88.                                "%d seconds after starting it" %
  89.                                int(time() - self.current_started))
  90.             return
  91.         if self.peerid is None:
  92.             self.peerid = self.wanted_peerid
  93.             self.url = self._makeurl(self.peerid, self.port)
  94.             self._announce(0)
  95.             return
  96.         if self.peerid != self.wanted_peerid:
  97.             self._announce(2)
  98.             self.peerid = None
  99.             self.previous_up = self.up()
  100.             self.previous_down = self.down()
  101.             return
  102.         if self.finish:
  103.             self.finish = False
  104.             self._announce(1)
  105.             return
  106.         if self.fail_wait is not None:
  107.             if self.last_time + self.fail_wait <= time():
  108.                 self._announce()
  109.             return
  110.         if self.last_time > time() - self.config['rerequest_interval']:
  111.             return
  112.         if self.ever_got_incoming():
  113.             getmore = self.howmany() <= self.config['min_peers'] / 3
  114.         else:
  115.             getmore = self.howmany() < self.config['min_peers']
  116.         if getmore or time() - self.last_time > self.announce_interval:
  117.             self._announce()
  118.  
  119.     def _announce(self, event=None):
  120.         self.current_started = time()
  121.         s = ('%s&uploaded=%s&downloaded=%s&left=%s' %
  122.             (self.url, str(self.up() - self.previous_up),
  123.              str(self.down() - self.previous_down), str(self.amount_left())))
  124.         if self.last is not None:
  125.             s += '&last=' + quote(str(self.last))
  126.         if self.trackerid is not None:
  127.             s += '&trackerid=' + quote(str(self.trackerid))
  128.         if self.howmany() >= self.config['max_initiate']:
  129.             s += '&numwant=0'
  130.         else:
  131.             s += '&compact=1'
  132.         if event is not None:
  133.             s += '&event=' + ['started', 'completed', 'stopped'][event]
  134.         Thread(target=self._rerequest, args=[s, self.peerid], \
  135.                name="Bittorrent rerequester -- %s" % self.baseurl).start()
  136.  
  137.     # Must destroy all references that could cause reference circles
  138.     def cleanup(self):
  139.         self.sched = None
  140.         self.howmany = None
  141.         self.connect = None
  142.         self.externalsched = lambda *args: None
  143.         self.amount_left = None
  144.         self.up = None
  145.         self.down = None
  146.         self.errorfunc = None
  147.         self.upratefunc = None
  148.         self.downratefunc = None
  149.         self.ever_got_incoming = None
  150.         self.diefunc = None
  151.         self.successfunc = None
  152.  
  153.     def _rerequest(self, url, peerid):
  154.         if self.config['ip']:
  155.             url += '&ip=' + gethostbyname(self.config['ip'])
  156.         request = Request(url)
  157.         if self.config['tracker_proxy']:
  158.             request.set_proxy(self.config['tracker_proxy'], 'http')
  159.         try:
  160.             h = urlopen(request)
  161.             data = h.read()
  162.             h.close()
  163.         # urllib2 can raise various crap that doesn't have a common base
  164.         # exception class especially when proxies are used, at least
  165.         # ValueError and stuff from httplib
  166.         except Exception, e:
  167.             def f(r='Problem connecting to tracker - ' + str(e)):
  168.                 self._postrequest(errormsg=r, peerid=peerid)
  169.         else:
  170.             def f():
  171.                 self._postrequest(data, peerid=peerid)
  172.         self.externalsched(f, 0)
  173.  
  174.     def _fail(self):
  175.         if self.fail_wait is None:
  176.             self.fail_wait = 50
  177.         else:
  178.             self.fail_wait *= 1.4 + random() * .2
  179.         self.fail_wait = min(self.fail_wait,
  180.                                 self.config['max_announce_retry_interval'])
  181.  
  182.     def _postrequest(self, data=None, errormsg=None, peerid=None):
  183.         self.current_started = None
  184.         self.last_time = time()
  185.         if errormsg is not None:
  186.             self.errorfunc(WARNING, errormsg)
  187.             self._fail()
  188.             return
  189.         try:
  190.             r = bdecode(data)
  191.             check_peers(r)
  192.         except BTFailure, e:
  193.             if data != '':
  194.                 self.errorfunc(ERROR, 'bad data from tracker - ' + str(e))
  195.             self._fail()
  196.             return
  197.         if r.has_key('failure reason'):
  198.             if self.howmany() > 0:
  199.                 self.errorfunc(ERROR, 'rejected by tracker - ' +
  200.                                r['failure reason'])
  201.             else:
  202.                 # sched shouldn't be strictly necessary
  203.                 def die():
  204.                     self.diefunc(CRITICAL, "Aborting the torrent as it was "
  205.                     "rejected by the tracker while not connected to any peers."
  206.                     " Message from the tracker:     " + r['failure reason'])
  207.                 self.sched(die, 0)
  208.             self._fail()
  209.         else:
  210.             self.fail_wait = None
  211.             if r.has_key('warning message'):
  212.                 self.errorfunc(ERROR, 'warning from tracker - ' +
  213.                                r['warning message'])
  214.             self.announce_interval = r.get('interval', self.announce_interval)
  215.             self.config['rerequest_interval'] = r.get('min interval',
  216.                                             self.config['rerequest_interval'])
  217.             self.trackerid = r.get('tracker id', self.trackerid)
  218.             self.last = r.get('last')
  219.             p = r['peers']
  220.             peers = []
  221.             if type(p) == str:
  222.                 for x in xrange(0, len(p), 6):
  223.                     ip = '.'.join([str(ord(i)) for i in p[x:x+4]])
  224.                     port = (ord(p[x+4]) << 8) | ord(p[x+5])
  225.                     peers.append((ip, port, None))
  226.             else:
  227.                 for x in p:
  228.                     peers.append((x['ip'], x['port'], x.get('peer id')))
  229.             ps = len(peers) + self.howmany()
  230.             if ps < self.config['max_initiate']:
  231.                 if self.doneflag.isSet():
  232.                     if r.get('num peers', 1000) - r.get('done peers', 0) > ps * 1.2:
  233.                         self.last = None
  234.                 else:
  235.                     if r.get('num peers', 1000) > ps * 1.2:
  236.                         self.last = None
  237.             for x in peers:
  238.                 self.connect((x[0], x[1]), x[2])
  239.             if peerid == self.wanted_peerid:
  240.                 self.successfunc()
  241.             self._check()
  242.